# coding:utf-8
from typing import Union

from PySide6.QtCore import QTimer, QUrl, Signal, QObject
from PySide6.QtGui import QImage
from PySide6.QtMultimedia import QMediaPlayer
from PySide6.QtWidgets import QWidget
from ffpyplayer.pic import Image
from ffpyplayer.player import MediaPlayer

from ..common import logger


class FFmpegMediaPlayer(QObject):
    """
    ffmpeg media player
    """
    sourceChanged = Signal(QUrl)
    mediaStatusChanged = Signal(QMediaPlayer.MediaStatus)
    positionChanged = Signal(int)
    durationChanged = Signal(int)
    metaDataChanged = Signal(dict)
    playbackStateChanged = Signal(QMediaPlayer.PlaybackState)
    playingChanged = Signal(bool)
    errorChanged = Signal(QMediaPlayer.Error)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.__source: QUrl = QUrl()
        self.__playerWidget: QWidget = None
        self.__mediaStatus: QMediaPlayer.MediaStatus = QMediaPlayer.MediaStatus.NoMedia
        self.__position: int = 0
        self.__duration: int = 0
        self.__metaData: dict = {}
        self.__error: QMediaPlayer.Error = QMediaPlayer.Error.NoError
        self.__errorString: str = ''

        self.timer = QTimer(self)
        self.player: MediaPlayer = None

        self.timer.timeout.connect(self._update_frame)

    def setSource(self, source: Union[str, QUrl]):
        if isinstance(source, QUrl):
            source = source.toString()
        if self.player:
            self.player.close_player()
            self.timer.stop()
            self.player = None
        logger.debug(f'set source: {source}')
        self.player = MediaPlayer(
            source,
            ff_opts={
                'paused': True,
                'autoexit': True,
                'vn': False,
                'sn': False,
                'aud': 'sdl'
            },
            loglevel='debug',
            callback=self.__callback
        )
        self.__source = QUrl(source)
        self.sourceChanged.emit(self.__source)

    def source(self) -> QUrl:
        return self.__source

    def fps(self) -> float:
        fps = self.metadata()["frame_rate"][0] / self.metadata()["frame_rate"][1]
        return fps

    def close(self):
        self.player.close_player()
        logger.debug('player closed')

    def play(self):
        self.player.set_pause(False)
        self.timer.start()
        self.playingChanged.emit(True)
        self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PlayingState)
        logger.debug('player playing')

    def pause(self):
        self.player.set_pause(True)
        self.timer.stop()
        self.playingChanged.emit(False)
        self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PausedState)
        logger.debug('player paused')

    def stop(self):
        self.player.set_pause(True)
        self.timer.stop()
        self.playingChanged.emit(False)
        self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.StoppedState)
        logger.debug('player stopped')

    def toggle(self):
        logger.debug('toggle player')
        self.player.toggle_pause()
        if self.isPaused():
            self.timer.stop()
            self.playingChanged.emit(False)
            self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PausedState)
            logger.debug('player paused')
        else:
            self.timer.start()
            self.playingChanged.emit(True)
            self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PlayingState)
            logger.debug('player playing')

    def isPlaying(self) -> bool:
        return not self.player.get_pause()

    def isPaused(self) -> bool:
        return self.player.get_pause()

    def setPosition(self, position: int):
        if self.player is None:
            return
        logger.debug(f'set position: {position}')
        self.player.seek(position, relative=False)

    def position(self) -> int:
        return self.player.get_pts()

    def duration(self) -> int:
        return int(self.metadata().get('duration', 0))

    def __setPosition(self, position: Union[float, int]):
        if self.player is None:
            return
        position = int(position)
        if self.__position == position:
            return
        self.__position = position
        self.positionChanged.emit(position)

    def metaData(self) -> dict:
        meta = self.player.get_metadata()
        if meta != self.__metaData:
            self.__metaData = meta
            self.metaDataChanged.emit(meta)
        return meta

    def setVolume(self, volume: int):
        if self.player is None:
            return
        logger.debug(f'set volume: {volume}')
        self.player.set_volume(volume / 100)

    def volume(self) -> int:
        return int(self.player.get_volume() * 100)

    def setMuted(self, muted: bool):
        if self.player is None:
            return
        logger.debug(f'set muted: {muted}')
        self.player.set_mute(muted)

    def isMuted(self) -> bool:
        return self.player.get_mute()

    def setOutputPixFormat(self, pix_fmt: str):
        self.player.set_output_pix_fmt(pix_fmt)

    def outputPixFormat(self) -> str:
        return self.player.get_output_pix_fmt()

    def metadata(self) -> dict:
        return self.player.get_metadata()

    def __setMediaStatus(self, status: QMediaPlayer.MediaStatus):
        if status == self.__mediaStatus:
            return
        logger.debug(f'set media status: {status}')
        self.__mediaStatus = status
        self.mediaStatusChanged.emit(status)

    def mediaStatus(self) -> QMediaPlayer.MediaStatus:
        return self.__mediaStatus

    def _update_frame(self):
        frame, val = self.player.get_frame()
        if frame is None:
            self.__setMediaStatus(QMediaPlayer.MediaStatus.LoadingMedia)
        if val == 'eof':
            # 结束状态处理
            self.__setMediaStatus(QMediaPlayer.MediaStatus.EndOfMedia)
            self.stop()
            return

        if not frame:
            return
        self.__setMediaStatus(QMediaPlayer.MediaStatus.LoadedMedia)
        img: Image
        tm: int
        img, tm = frame
        interval = round(1000 / self.fps())
        if self.timer.interval() != interval:
            logger.debug(f'set timer interval: {interval}')
            self.timer.setInterval(interval)
        w, h = img.get_size()
        self.__setPosition(tm)
        if self.__duration != self.duration():
            self.durationChanged.emit(self.duration())
        self.metaData()
        qimage = QImage(img.to_bytearray(True)[0], w, h, QImage.Format.Format_RGB888)
        self.__playerWidget.setImage(qimage)

    def setVideoOutput(self, widget: QWidget):
        self.__playerWidget = widget
        logger.debug(f'set video output: {widget}')
        if not hasattr(widget, 'setImage'):
            logger.error('视频输出小部件必须有 `setImage` 方法')
            raise ValueError('视频输出小部件必须有 `setImage` 方法')

    def errorString(self) -> str:
        return self.__errorString

    def __setError(self, error: QMediaPlayer.Error):
        if self.__error == error:
            return
        self.__error = error
        self.errorChanged.emit(error)

    def error(self) -> QMediaPlayer.Error:
        return self.__error

    def __callback(self, *args, **kwargs):
        tp, status = args[0].split(':')
        if tp == 'read':
            if status == 'error':
                self.__errorString = '资源读取错误'
                self.__setMediaStatus(QMediaPlayer.MediaStatus.InvalidMedia)
                self.__setError(QMediaPlayer.Error.ResourceError)
                self.stop()
                self.close()
            elif status == 'exit':
                self.__errorString = '播放结束'
                self.__setMediaStatus(QMediaPlayer.MediaStatus.EndOfMedia)
                self.stop()
                self.close()
        elif tp == 'audio':
            if status == 'error':
                self.__errorString = '音频播放错误'
                self.__setError(QMediaPlayer.Error.ResourceError)
                self.stop()
                self.close()
            elif status == 'exit':
                self.__errorString = '音频播放结束'
                self.stop()
                self.close()
        elif tp == 'video':
            if status == 'error':
                self.__errorString = '视频播放错误'
                self.__setError(QMediaPlayer.Error.ResourceError)
                self.stop()
                self.close()
            elif status == 'exit':
                self.__errorString = '视频播放结束'
                self.stop()
                self.close()
